package com.monochromebytes.mononet.test;
import java.util.ArrayList;

import org.newdawn.slick.geom.Line;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;

public class Collision {
    private Vector2f   normal;
    private Vector2f   victimIntersection;
    private Vector2f   invaderIntersection;
    private Collidable invader;
    private Collidable victim;
    
    private float delta;

    public Collision(Collidable collidableA, Collidable collidableB) {
        this.delta = this.findCollisionShape(collidableA, collidableB);
        this.calculateIntersection(collidableA, collidableB);
    }

    private void calculateIntersection(Collidable collidableA, Collidable collidableB) {
        Vector2f edge1 = new Vector2f();
        Vector2f point1 = new Vector2f();
        float distance1 = this
            .calculateShortestPoint(collidableA, collidableB, edge1, point1);

        Vector2f edge2 = new Vector2f();
        Vector2f point2 = new Vector2f();
        float distance2 = this
            .calculateShortestPoint(collidableB, collidableA, edge2, point2);

        if (distance1 < distance2) {
            this.invaderIntersection = edge1;
            this.victimIntersection = point1;
            this.normal = edge1.copy().sub(point1).normalise();

            this.invader = collidableA;
            this.victim = collidableB;
        }
        else {
            this.invaderIntersection = edge2;
            this.victimIntersection = point2;
            this.normal = edge2.copy().sub(point2).normalise();

            this.invader = collidableB;
            this.victim = collidableA;
        }
    }

    private float calculateShortestPoint(Collidable collidableA, Collidable collidableB, Vector2f shortestEdge, Vector2f shortestPoint) {
        ArrayList<Vector2f> edgesOfA = getEdgesAsVectors(collidableA);
        ArrayList<Line> linesOfB = getBorderLines(collidableB);

        float shortestDistance = Float.MAX_VALUE;

        Vector2f point = new Vector2f();
        for (Vector2f edge : edgesOfA) {
            for (Line line : linesOfB) {
                line.getClosestPoint(edge, point);

                float distance = edge.distance(point);

                if (distance < shortestDistance) {
                    shortestEdge.set(edge);
                    shortestPoint.set(point);
                    shortestDistance = distance;
                }
            }
        }

        return shortestDistance;
    }

    private ArrayList<Line> getBorderLines(Collidable collidable) {
        ArrayList<Line> lines = new ArrayList<Line>();
        Shape shape = collidable.calculateShape(collidable.getShape(), this.delta);

        int size = shape.getPointCount();
        for (int i = 0; i < size; i++) {
            Line line;
            if (i == size - 1) {
                line = new Line(new Vector2f(shape.getPoint(i)), new Vector2f(shape.getPoint(0)));
            }
            else {
                line = new Line(new Vector2f(shape.getPoint(i)),
                    new Vector2f(shape.getPoint(i + 1)));
            }
            lines.add(line);
        }
        return lines;
    }

    private ArrayList<Vector2f> getEdgesAsVectors(Collidable collidable) {
        ArrayList<Vector2f> edges = new ArrayList<Vector2f>();
        Shape shape = collidable.calculateShape(collidable.getShape(), this.delta);

        int size = shape.getPointCount();
        for (int i = 0; i < size; i++) {
            edges.add(new Vector2f(shape.getPoint(i)));
        }
        return edges;
    }
    
    private float findCollisionShape(Collidable collidableA, Collidable collidableB) {
        float delta = Server.delta;
        float step = delta * 0.5f;
        
        int i = 0;
        while (step > 0.01) {
            if (Collider.isColliding(collidableA, collidableB, delta)) {
                delta -= step;
            } else {
                delta += step;
            }
            step *= 0.5f;
            i++;
        }
        
        return delta;
    }

    public Collidable getInvader() {
        return this.invader;
    }

    public Collidable getVictim() {
        return this.victim;
    }

    public Vector2f getNormal() {
        return this.normal;
    }

    public Vector2f getInvaderIntersection() {
        return this.invaderIntersection;
    }

    public Vector2f getVictimIntersection() {
        return this.victimIntersection;
    }
}
